/**
 * CaddieController.java 21 juil. 07
 *
 * Copyright (c) 2007 windu.2b <jcaddie@windu2b.org>. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.windu2b.jcaddie.swing;

import java.util.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.undo.*;

import org.windu2b.jcaddie.model.*;

/**
 * @author windu.2b
 * 
 */
public class CaddieController
{
	private Caddie	            caddie;


	private UserPreferences	    preferences;


	private CaddieApplication	application;


	private JComponent	        caddieView, focusedView;


	private CatalogController	catalogController;


	private ProductController	productController;


	private TotalController	    totalController;


	private StatusBarController	statusBarController;


	private UndoableEditSupport	undoSupport;


	private UndoManager	        undoManager;


	private CaddiePane	        view;


	private ResourceBundle	    resource;


	private int	                saveUndoLevel;




	public CaddieController( Caddie caddie, CaddieApplication application )
	{
		this( caddie, application.getUserPreferences(), application );
	}




	public CaddieController( Caddie caddie, UserPreferences preferences )
	{
		this( caddie, preferences, null );
	}




	private CaddieController( Caddie caddie, UserPreferences preferences,
	        CaddieApplication application )
	{
		this.caddie = caddie;
		this.preferences = preferences;
		this.application = application;
		this.undoSupport = new UndoableEditSupport();
		this.undoManager = new UndoManager();
		undoSupport.addUndoableEditListener( undoManager );

		resource = ResourceBundle.getBundle( CaddieController.class.getName() );

		this.catalogController = new CatalogController( preferences
		        .getCatalog(), preferences, undoSupport );
		this.productController = new ProductController( caddie, preferences,
		        undoSupport );
		this.totalController = new TotalController( caddie, preferences );
		this.statusBarController = new StatusBarController( caddie, preferences );

		this.caddieView = new CaddiePane( caddie, preferences, this );

		view = ( ( CaddiePane ) getView() );

		addListeners();
		enableDefaultActions( ( CaddiePane ) caddieView );
	}




	public JComponent getView()
	{
		return caddieView;
	}




	public CatalogController getCatalogController()
	{
		return catalogController;
	}




	public void addCaddieProduct()
	{
		List<CatalogPieceOfProduct> selectedProduct = preferences.getCatalog()
		        .getSelectedProduct();

		if ( !selectedProduct.isEmpty() )
		{
			List<CaddiePieceOfProduct> newProduct = new ArrayList<CaddiePieceOfProduct>();

			for ( CatalogPieceOfProduct piece : selectedProduct )
			{
				newProduct.add( new CaddiePieceOfProduct( piece ) );
			}

			getProductController().addProduct( newProduct );
		}
	}




	public void deleteCaddieProduct()
	{
		List<CatalogPieceOfProduct> selectedProduct = preferences.getCatalog()
		        .getSelectedProduct();

		if ( !selectedProduct.isEmpty() )
		{
			List<CaddiePieceOfProduct> newProduct = new ArrayList<CaddiePieceOfProduct>();

			for ( CatalogPieceOfProduct piece : selectedProduct )
			{
				newProduct.remove( new CaddiePieceOfProduct( piece ) );
			}

			getProductController().deleteSelection();
		}
	}




	/**
	 * @return the controller of courses list.
	 */
	public ProductController getProductController()
	{
		return productController;
	}




	/**
	 * Méthode permettant d'annuler une action
	 */
	public void undo()
	{
		undoManager.undo();
		CaddiePane view = ( ( CaddiePane ) getView() );

		boolean moreUndo = undoManager.canUndo();
		view.setEnabled( CaddiePane.ActionType.UNDO, moreUndo );
		view.setEnabled( CaddiePane.ActionType.REDO, true );

		if ( moreUndo )
		{
			view.setUndoRedoName( undoManager.getUndoPresentationName(),
			        undoManager.getRedoPresentationName() );
		}
		else
		{
			view.setUndoRedoName( null, undoManager.getRedoPresentationName() );
		}
		saveUndoLevel--;
		caddie.setModified( saveUndoLevel != 0 );
	}




	public void redo()
	{
		undoManager.redo();

		boolean moreRedo = undoManager.canRedo();
		view.setEnabled( CaddiePane.ActionType.UNDO, true );
		view.setEnabled( CaddiePane.ActionType.REDO, moreRedo );

		if ( moreRedo )
		{
			view.setUndoRedoName( undoManager.getUndoPresentationName(),
			        undoManager.getRedoPresentationName() );
		}
		else
		{
			view.setUndoRedoName( undoManager.getUndoPresentationName(), null );
		}
		saveUndoLevel++;
		caddie.setModified( saveUndoLevel != 0 );
	}




	public void delete()
	{
		if ( focusedView == getProductController().getView() )
			getProductController().deleteSelection();
		else if ( focusedView == getProductController().getView() )
		    getProductController().deleteSelection();
	}




	/**
	 * Méthode appelée par l'action <code>NEW_CADDIE</code>
	 */
	public void newCaddie()
	{
		application.addCaddie( new Caddie() );
	}




	/**
	 * Méthode appelée par l'action <code>OPEN</code> pour ouvrir un caddie.
	 * Cette méthode affiche une
	 * {@link CaddiePane#showOpenDialog() fenêtre de dialogue} dans la vue, lit
	 * le caddie à partir du nom choisi et l'ajoute à la liste des caddies de
	 * l'application.
	 */
	public void open()
	{
		final String caddieName = ( ( CaddiePane ) getView() ).showOpenDialog();
		if ( caddieName != null )
		{
			open( caddieName );
		}
	}




	/**
	 * Ouvre le caddie dont le nom de fichier est <code>caddieName</code>
	 */
	public void open( String caddieName )
	{
		// Vérifie si un caddie n'est pas déjà ouvert
		for ( Caddie caddie : this.application.getCaddies() )
		{
			if ( caddieName.equals( caddie.getName() ) )
			{
				String message = String.format( this.resource
				        .getString( "alreadyOpen" ), caddieName );
				( ( CaddiePane ) getView() ).showMessage( message );
				return;
			}
		}
		try
		{
			Caddie openedCaddie = application.getCaddieRecorder().readCaddie(
			        caddieName );
			openedCaddie.setName( caddieName );
			application.addCaddie( openedCaddie );
		}
		catch ( RecorderException ex )
		{
			String message = String.format( resource.getString( "openError" ),
			        caddieName );
			( ( CaddiePane ) getView() ).showError( message );
		}
	}




	/**
	 * Updates user preferences <code>recentCaddies</code> and write
	 * preferences.
	 */
	private void updateUserPreferencesRecentCaddies( List<String> recentCaddies )
	{
		if ( this.application != null )
		{
			try
			{
				// Check every recent caddie exists
				for ( Iterator<String> it = recentCaddies.iterator(); it
				        .hasNext(); )
				{
					try
					{
						if ( !this.application.getCaddieRecorder().exists(
						        it.next() ) )
						{
							it.remove();
						}
					}
					catch ( RecorderException ex )
					{
						// If caddieName can't be checked ignore it
					}
				}
				preferences.setRecentCaddies( recentCaddies );
				preferences.write();
			}
			catch ( RecorderException ex )
			{
				CaddiePane caddieView = ( CaddiePane ) getView();

				if ( caddieView != null )
				{
					caddieView.showError( this.resource
					        .getString( "savePreferencesError" ) );
				}
			}
		}
	}




	/**
	 * Returns a list of displayable recent caddies.
	 */
	public List<String> getRecentCaddies()
	{
		if ( this.application != null )
		{
			List<String> recentCaddies = new ArrayList<String>();
			for ( String caddieName : preferences.getRecentCaddies() )
			{
				try
				{
					if ( this.application.getCaddieRecorder().exists(
					        caddieName ) )
					{
						recentCaddies.add( caddieName );
					}
				}
				catch ( RecorderException ex )
				{
					// If caddieName can't be checked ignore it
				}
			}
			( ( CaddiePane ) getView() ).setEnabled(
			        CaddiePane.ActionType.DELETE_RECENT_CADDIES, !recentCaddies
			                .isEmpty() );
			return Collections.unmodifiableList( recentCaddies );
		}
		else
		{
			return new ArrayList<String>();
		}
	}




	/**
	 * Deletes the list of recent caddies in user preferences.
	 */
	public void deleteRecentCaddies()
	{
		updateUserPreferencesRecentCaddies( new ArrayList<String>() );
		( ( CaddiePane ) getView() ).setEnabled(
		        CaddiePane.ActionType.DELETE_RECENT_CADDIES, false );
	}




	public void close()
	{
		boolean willClose = true;
		if ( caddie.isModified() )
		{
			switch ( ( ( CaddiePane ) getView() )
			        .confirmSave( caddie.getName() ) )
			{
				case SAVE :
					willClose = save();
					break;

				case CANCEL :
					willClose = false;
					break;
			}
		}

		if ( willClose ) application.deleteCaddie( caddie );
	}




	public boolean save()
	{
		if ( caddie.getName() == null )
			return saveAs();
		else return save( caddie.getName() );
	}




	private boolean save( String caddieFileName )
	{
		try
		{
			application.getCaddieRecorder()
			        .writeCaddie( caddie, caddieFileName );
			String oldCaddieName = caddie.getName();
			caddie.setName( caddieFileName );
			saveUndoLevel = 0;
			caddie.setModified( false );
			// Update recent caddies list
			/*
			 * List<String> recentCaddies = new ArrayList<String>( preferences
			 * .getRecentCaddies() ); if ( caddieFileName.equals( oldCaddieName ) ) {
			 * recentCaddies.remove( oldCaddieName ); } recentCaddies.add( 0,
			 * caddieFileName ); updateUserPreferencesRecentCaddies(
			 * recentCaddies ); caddie.setModified( false );
			 */
			return true;
		}
		catch ( RecorderException ex )
		{
			ex.printStackTrace();
			String message = String.format( resource.getString( "saveError" ),
			        caddieFileName );
			( ( CaddiePane ) getView() ).showError( message );
			return false;
		}
	}




	public void preferences()
	{
		UserPreferencesPanel preferencesPanel = new UserPreferencesPanel();
		preferencesPanel.setPreferences( preferences );

		if ( preferencesPanel.showDialog( getView() ) )
		{
			// TODO compléter la liste des préférences à sauvegarder
			preferences.setUnit( preferencesPanel.getUnit() );

			try
			{
				preferences.write();
			}
			catch ( RecorderException ex )
			{
				view.showError( resource.getString( "savePreferencesError" ) );
			}
		}
	}




	public void exit()
	{
		for ( Caddie caddie : application.getCaddies() )
		{
			if ( caddie.isModified() )
			{
				if ( ( ( CaddiePane ) getView() ).confirmExit() )
					break;
				else return;
			}
		}
		for ( Caddie caddie : application.getCaddies() )
			application.deleteCaddie( caddie );
	}




	/**
	 * Saves the caddie managed by this controller with a different name. This
	 * method displays a {@link CaddiePane#showSaveDialog(String) save dialog}
	 * in view, and saves caddie with the choosen name if any. If this name
	 * already exists, the user will be
	 * {@link CaddiePane#confirmOverwrite(String) prompted} in view whether he
	 * wants to overwrite this existing name.
	 * 
	 * @return <code>true</code> if caddie was saved.
	 */
	public boolean saveAs()
	{
		String newName = ( ( CaddiePane ) getView() )
		        .showSaveDialog( this.caddie.getName() );
		if ( newName != null )
		{
			try
			{
				if ( !this.application.getCaddieRecorder().exists( newName )
				        || ( ( CaddiePane ) getView() )
				                .confirmOverwrite( newName ) )
				{
					return save( newName );
				}
				else
				{
					return saveAs();
				}
			}
			catch ( RecorderException ex )
			{
				String message = String.format( this.resource
				        .getString( "saveError" ), newName );
				( ( CaddiePane ) getView() ).showError( message );
			}
		}
		return false;
	}




	public void help()
	{

	}




	public void about()
	{
		view.showAboutDialog();
	}




	public void cut( List<? extends Object> items )
	{
		undoSupport.beginUpdate();
		getProductController().deleteItems( items );
		undoSupport.postEdit( new AbstractUndoableEdit()
		{
			/**
			 * 
			 */
			private static final long	serialVersionUID	= 0L;




			@Override
			public String getPresentationName()
			{
				return resource.getString( "undoCutName" );
			}
		} );
		undoSupport.endUpdate();
	}




	public void paste( final List<? extends Object> items )
	{
		addItems( items, 20, 20, resource.getString( "undoPasteName" ) );
	}




	public void drop( final List<? extends Object> items, float dx, float dy )
	{
		addItems( items, dx, dy, resource.getString( "undoDropName" ) );
	}




	private void addItems( final List<? extends Object> items, float dx,
	        float dy, final String presentationName )
	{
		if ( !items.isEmpty() )
		{
			undoSupport.beginUpdate();
			getProductController()
			        .addProduct( Caddie.getProductSubList( items ) );
			// getProductController().moveItems( items, dx, dy );
			getProductController().selectAndShowItems( items );

			undoSupport.postEdit( new AbstractUndoableEdit()
			{
				/**
				 * 
				 */
				private static final long	serialVersionUID	= 6299858868311716133L;




				@Override
				public void redo() throws CannotRedoException
				{
					super.redo();
					getProductController().selectAndShowItems( items );
				}




				@Override
				public String getPresentationName()
				{
					return presentationName;
				}
			} );

			undoSupport.endUpdate();
		}
	}




	public void focusedViewchanged( JComponent focusedView )
	{
		this.focusedView = focusedView;
		enableActionsOnSelection();
		enablePasteAction();
	}




	/**
	 * Selects everything in the focused component.
	 */
	public void selectAll()
	{
		if ( this.focusedView == getProductController().getView() )
		{
			getProductController().selectAll();
		}
		else if ( this.focusedView == getProductController().getView() )
		{
			getProductController().selectAll();
		}
	}




	/**
	 * Enables action bound to selection.
	 */
	private void enableActionsOnSelection()
	{
		// Search if selection contains at least one piece or one wall
		List<Object> selectedItems = this.caddie.getSelectedItems();
		boolean selectionContainsProduct = false;
		boolean selectionContainsOneCopiableObjectOrMore = false;
		boolean selectionContainsTwoPiecesOfProductOrMore = false;
		boolean selectionContainsWalls = false;
		selectionContainsProduct = !Caddie.getProductSubList( selectedItems )
		        .isEmpty();
		selectionContainsTwoPiecesOfProductOrMore = Caddie.getProductSubList(
		        selectedItems ).size() >= 2;
		selectionContainsOneCopiableObjectOrMore = selectionContainsProduct
		        || selectionContainsWalls;

		List catalogSelectedItems = this.preferences.getCatalog()
		        .getSelectedProduct();
		CaddiePane view = ( ( CaddiePane ) getView() );
		if ( this.focusedView == getCatalogController().getView() )
		{
			view.setEnabled( CaddiePane.ActionType.COPY, catalogSelectedItems
			        .isEmpty() );
			view.setEnabled( CaddiePane.ActionType.CUT, false );
			view.setEnabled( CaddiePane.ActionType.DELETE, false );
		}
		else if ( this.focusedView == getProductController().getView() )
		{
			view.setEnabled( CaddiePane.ActionType.COPY,
			        selectionContainsProduct );
			view.setEnabled( CaddiePane.ActionType.CUT,
			        selectionContainsProduct );
			view.setEnabled( CaddiePane.ActionType.DELETE,
			        selectionContainsProduct );
		}
		else if ( this.focusedView == getProductController().getView() )
		{
			view.setEnabled( CaddiePane.ActionType.COPY,
			        selectionContainsOneCopiableObjectOrMore );
			view.setEnabled( CaddiePane.ActionType.CUT,
			        selectionContainsOneCopiableObjectOrMore );
			view.setEnabled( CaddiePane.ActionType.DELETE,
			        selectionContainsOneCopiableObjectOrMore );
		}
		else
		{
			view.setEnabled( CaddiePane.ActionType.COPY, false );
			view.setEnabled( CaddiePane.ActionType.CUT, false );
			view.setEnabled( CaddiePane.ActionType.DELETE, false );
		}

		view.setEnabled( CaddiePane.ActionType.ADD_CADDIE_PRODUCT,
		        !catalogSelectedItems.isEmpty() );
		// In creation mode all actions bound to selection are disabled
		view.setEnabled( CaddiePane.ActionType.DELETE_CADDIE_PRODUCT,
		        selectionContainsProduct );
		view.setEnabled( CaddiePane.ActionType.DELETE_SELECTION,
		        selectionContainsOneCopiableObjectOrMore );
		view.setEnabled( CaddiePane.ActionType.MODIFY_CADDIE_PRODUCT,
		        selectionContainsProduct );
	}




	public void enablePasteAction()
	{
		CaddiePane view = ( ( CaddiePane ) getView() );
		view.setEnabled( CaddiePane.ActionType.PASTE, !view.isClipboardEmpty() );
	}




	/**
	 * Chargement des différents listeners, permettant de rendre accessible ou
	 * non les boutons et actions des menus
	 */
	private void addListeners()
	{
		/*
		 * Appel des listeners pour le menu 'FILE'
		 */
		addNewFileListener();
		addOpenFileListener();
		addSaveFileListener();
		addSaveAsFileListener();
		addQuitListener();

		/*
		 * Appel des listeners pour le menu 'EDIT'
		 */
		addUndoSupportListener();

		/*
		 * Appel des listeners pour le menu 'PRODUCT'
		 */
		addCatalogSelectionListener();
		addCaddieSelectionListener();

		/*
		 * Appel des listeners pour le menu 'HELP'
		 */
		addHelpListener();
		addAboutListener();
	}




	private void addNewFileListener()
	{
		view.setEnabled( CaddiePane.ActionType.NEW_CADDIE, true );
	}




	private void addOpenFileListener()
	{
		view.setEnabled( CaddiePane.ActionType.OPEN, true );
	}




	private void addSaveFileListener()
	{
		view.setEnabled( CaddiePane.ActionType.SAVE, true );
	}




	private void addSaveAsFileListener()
	{
		view.setEnabled( CaddiePane.ActionType.SAVE_AS, true );
	}




	private void addQuitListener()
	{
		view.setEnabled( CaddiePane.ActionType.EXIT, true );
	}




	/**
	 * Ajoute un listener pour gérer l'annulation d'une action L'action
	 * <code>UNDO</code> ne devient accessible que si une action a
	 * préalablement été faite L'action <code>REDO</code> ne devient
	 * accessible que si une action a préalablement été annulée
	 */
	private void addUndoSupportListener()
	{
		undoSupport.addUndoableEditListener( new UndoableEditListener()
		{
			public void undoableEditHappened( UndoableEditEvent ev )
			{
				view.setEnabled( CaddiePane.ActionType.UNDO, true );
				view.setEnabled( CaddiePane.ActionType.REDO, false );

				view.setUndoRedoName( ev.getEdit().getUndoPresentationName(),
				        null );
				saveUndoLevel++;
				caddie.setModified( true );
			}
		} );
	}




	/**
	 * Ajoute un listener pour gérer l'insertion depuis le catalogue vers la
	 * liste. L'action <code>ADD_CADDIE_PRODUCT</code> ne devient accessible
	 * que si des éléments du catalogue ont été sélectionnés
	 */
	private void addCatalogSelectionListener()
	{
		preferences.getCatalog().addSelectionListener( new SelectionListener()
		{
			public void selectionChanged( SelectionEvent ev )
			{
				view.setEnabled( CaddiePane.ActionType.ADD_CADDIE_PRODUCT, !ev
				        .getSelectedItems().isEmpty() );
			}
		} );
	}




	/**
	 * Ajoute un listener pour gérer le retrait d'éléments dans la liste
	 * L'action <code>DELETE_CADDIE_PRODUCT</code> ne devient accessible que
	 * si des éléments de la liste ont été sélectionnés
	 */
	private void addCaddieSelectionListener()
	{
		caddie.addSelectionListener( new SelectionListener()
		{
			public void selectionChanged( SelectionEvent ev )
			{
				boolean selectionContainsProduct = false;

				for ( Object item : ev.getSelectedItems() )
				{
					if ( item instanceof CaddiePieceOfProduct )
					{
						selectionContainsProduct = true;
						break;
					}
				}

				view.setEnabled( CaddiePane.ActionType.DELETE_CADDIE_PRODUCT,
				        selectionContainsProduct );
			}
		} );
	}




	private void addHelpListener()
	{
		view.setEnabled( CaddiePane.ActionType.HELP, true );
	}




	private void addAboutListener()
	{
		view.setEnabled( CaddiePane.ActionType.ABOUT, true );
	}




	/**
	 * Permet de définir quelles sont les actions qui sont accessibles par
	 * défaut. Certaines actions sont dépendantes de la présence d'une
	 * <code>CaddieApplication</code>
	 * 
	 * @param caddieView
	 */
	private void enableDefaultActions( CaddiePane caddieView )
	{
		boolean applicationExists = this.application != null;
		caddieView.setEnabled( CaddiePane.ActionType.NEW_CADDIE, true );
		caddieView.setEnabled( CaddiePane.ActionType.OPEN, true );
		caddieView.setEnabled( CaddiePane.ActionType.CLOSE, applicationExists );
		caddieView.setEnabled( CaddiePane.ActionType.SAVE, applicationExists );
		caddieView
		        .setEnabled( CaddiePane.ActionType.SAVE_AS, applicationExists );
		caddieView.setEnabled( CaddiePane.ActionType.EXIT, true );
		caddieView.setEnabled( CaddiePane.ActionType.PREFERENCES, true );
		caddieView.setEnabled( CaddiePane.ActionType.ABOUT, true );
	}




	/**
	 * 
	 */
	public void editPreferences()
	{
		// TODO Auto-generated method stub

	}




	/**
	 * @return Le contrôleur de la zone d'affichage du prix total
	 */
	public TotalController getTotalController()
	{
		return totalController;
	}




	/**
	 * @return le contrôleur de la Status Bar
	 */
	public StatusBarController getStatusBarController()
	{
		return statusBarController;
	}
}